Skip to content

fix(objectql, runtime)!: a function's bare-name read is refused naming both when the platform and a package hold the name, and answers nothing for a name only a disabled package holds (ADR-0150 D4) - #33439

Queued
objectstack-fleet[bot] wants to merge 6 commits into
mainfrom
claude/area-E2b-32907

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #32907
Fixes #32933
Fixes #32910

Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — skills/** and content/docs/** name neither resolveFunction/resolveFunctionEntry nor resolvePackageScopedFunction, and teach no function name held by the platform beside a package or by a disabled package; the prescription ships in the changeset's BREAKING paragraph.

维护者速读

  1. 改了什么:不带包名按名读函数(resolveFunction / resolveFunctionEntry,以及无调用包的 resolvePackageScopedFunction)时,平台(无主注册)与一个包同持一个名字,一律 409 点名双方,不再按注册先后答其一;只有已停用包持有的名字不再答该包的函数(停用包仍计为持有者,照样参与歧义判定)。运行时重绑从持有者集读回保留函数,不再走裸读。另补 objectql: runtime pins for a function's bare-name claim release — pre-seal re-bind, move-path ordering, owner-spelling order (#32877 follow-up) #32910 的四组运行时钉子。
  2. 为什么改:ADR-0150 D4 规定无包调用方在平台+包同名时应被拒并点名双方,原实现只数包,按先后答或答空;ruling A(6824698376)要求停用包"计为持有者、永不被解析到"。
  3. 风险与回滚:只收窄(Clause-② no (narrowing),标题带 !),原先被拒的一律仍拒,无任何放宽;仍会答的:单一启用包、仅平台、调用方自身包。第一方:仓内无平台与包同名的函数注册(见 Acceptance notes)。回滚即还原本 PR。
  4. 先看:packages/objectql/src/package-scoped-resolver.ts 的 callerlessFunctionResolution;packages/objectql/src/engine-hooks-and-functions.ts 的 bareFunctionEntry;packages/runtime/src/app-artifact-handlers.ts 的 releaseMovedFunctions 中 bare: 一行。
  5. 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-request-family.test.ts,预期 78/78 绿;其中 [#32907]、[#32933] 两组在 main 上 5 条红。
  6. 需要你做的:裁定 objectql: the engine's function holder set follows ruling A's served-holders default #32933 的读法(见 Acceptance notes 的 needs_decision:本 PR 实施"计数不解析",另一读法"只数在用持有者"会放宽两个格子)。

What changes

The engine keeps every holder of a function name: each package that registered one, and the platform for a registration under no package (ObjectQL#functionHoldersOf). A read that names no package resolves the name by ADR-0150 D3 over those holders.

  1. objectql: a function's bare-name read refuses with 409 naming both when the platform and one package hold the name (ADR-0150 D4) #32907 — the platform beside a package is two holders. bareFunctionEntry (packages/objectql/src/engine-hooks-and-functions.ts, the body of ObjectQL#resolveFunction / resolveFunctionEntry) now always runs the rule (resolvePackageScopedHolders, caller with no package) over the holder set, platform included. Before, it consulted the rule only when two PACKAGES held the name, and otherwise answered the bare map's last registration, so the platform plus one package answered by order (the package's entry, or the platform's when it registered last) or nothing after a third, last-registered package left. Now every one of those cells is 409 RESOURCE_CONFLICT, reason ambiguous-in-environment, candidates [package, platform] — the same refusal the caller-less resolvePackageScopedFunction already answered, so the two reads agree. When the rule resolves (one holder), the answer is still the bare map's entry, the same entry object, so the one-holder answer is unchanged byte for byte. PR fix(objectql, runtime): a function's bare-name record follows its holders — a re-bind releases the old owner's claim with the function, and the one remaining holder answers the bare read #32877's CONTROL pin for this row is flipped to the verdict.
  2. objectql: the engine's function holder set follows ruling A's served-holders default #32933 — a disabled package is counted, never resolved to. New callerlessFunctionResolution (packages/objectql/src/package-scoped-resolver.ts, not re-exported from the package entry) post-filters the rule's answer for a caller with no package: when the rule picked a package as the environment's unique holder (via: 'unique-holder', the step only a caller with no package reaches) and the registry says that package is disabled (SchemaRegistry#isPackageDisabled), the answer is not-found. Both the bare read and resolvePackageScopedFunction apply it. Refusals are computed over EVERY holder, the disabled one included, so a disable never turns a refused name into an answer. A caller naming its own package, a caller's closure step, the platform's item and every refusal pass through unchanged. The every-holder doors (the hook binder's bind after the seal, claimBareNameFunctions; the cold-boot seal, sealBareNameHolders) read the registry's claims, which never looked at enablement; they are pinned, not changed.
  3. Runtime re-bind — read the kept entry off the holder set. releaseMovedFunctions (packages/runtime/src/app-artifact-handlers.ts) read the bare slot of each kept name through resolveFunctionEntry. With (1) that read throws for a name the platform holds beside the package — aborting the re-bind's teardown — and with (2) it answers nothing for a disabled package, which would drop the owner spelling the package held a kept function by. For a name the package alone holds the bare slot IS the package's holder entry (one entry object in both maps), so the binder now uses that entry; the two-package case (packageHolders.length > 1) is unchanged.
  4. objectql: runtime pins for a function's bare-name claim release — pre-seal re-bind, move-path ordering, owner-spelling order (#32877 follow-up) #32910 — runtime pins only (no code): the re-bind before the seal (N4), the move path's ordering CONTROL (D2-N1), the other owner-spelling order (D2-N2).

No packages/objectql/src/registry.ts symbol is changed (registry_symbols: none). No export is added; engine.ts changes docblocks only.

Tests

Pins, numbered for this PR (no numeral lives in the files; the skipped (iii) of #32910's "Numbering" item was in PR #32877's body only, so there is nothing to renumber in the file):

  • objectql src/registry-bare-name-one-holder-request-family.test.ts
    • (i) flipped: [#32534] … the platform and one package remain after the last-registered package leaves … (FROM nothing TO 409) — was the CONTROL "current behaviour, see objectql: a function's bare-name read refuses with 409 naming both when the platform and one package hold the name (ADR-0150 D4) #32907".
    • (ii) [#32907] … the package registered last (FROM the package's entry TO 409).
    • (iii) [#32907] … the platform registered last (FROM the platform's entry TO 409) — the platform-last-registrant cell.
    • (iv) [#32933] a name only a disabled package holds: the bare read and the caller-less read answer nothing; enabled again, both answer it.
    • (v) [#32933] counted: the platform beside a disabled package is still two holders.
    • CONTROLs: the platform alone and one package alone answer; a caller package holding the name resolves its own beside the platform; the disabled package's own read resolves its own; two packages, one disabled, stay refused naming both; after the seal a co-owner's bind of a name a disabled package holds is refused naming it; the cold-boot seal reports a co-owner clash with one holder disabled.
  • runtime src/app-artifact-handlers.test.ts
    • (vi) the teardown it.each gains a row: the platform holds the name beta keeps too — the teardown runs whole, the bare read is refused [beta, platform].
    • (vii) N4, it.each moved / dropped: a re-bind before the seal releases the old owner's claim; the seal then reports no clash.
    • (viii) D2-N1 CONTROL: the move path releases after the old owner's kept functions are registered again — a function the new bind neither moves nor gives back keeps its claim.
    • (ix) D2-N2, it.each moved / dropped: the bare-id registration before the bind; the removal takes the entry the bind replaced it with and the claim is released.
    • (x) CONTROL: a disabled old owner keeps a kept function under the bare-id spelling it held it by, and its claim.

The red-on-main reading and the ablations ran before the merge (the reading on 90423f6b, every ablation on e8106eb3); the merge touched none of the files they read.

Red on main / green after: (i)–(v) are red on main's objectql sources and green on this head; every objectql CONTROL is green on both. (vi)–(x) pin behaviour main already has (the #32910 rows are coverage of #32534's release; (vi) and (x) guard the re-bind against this PR's engine change), so each is proven by its ablation instead.

Red-on-main reading (objectql sources restored from df9fe94, test file at head)
pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/registry-bare-name-one-holder-request-family.test.ts
Tests  5 failed | 73 passed (78)
  x the platform and one package remain after the last-registered package leaves ... (FROM nothing TO 409)
  x the package registered last (FROM the package's entry TO 409)
  x the platform registered last (FROM the platform's entry TO 409)
  x a name only a disabled package holds: ... (FROM its entry TO nothing)
  x counted: the platform beside a disabled package is still two holders ...
restore: git checkout HEAD -- (3 paths); git diff HEAD empty, git status clean
Ablations — one per pin, each through scripts/ablation-replace.mjs (anchor hit x1, blob changed, restored to the HEAD blob, git diff HEAD empty)

Neither suite reads a rebuilt dist/ for the mutated file: the objectql file imports objectql src/, and every runtime mutation is in runtime src/ (objectql dist/ was built once from this head's objectql sources before the runtime runs).

A1  objectql bareFunctionEntry: hide the platform from the holder set
    ({ ...functionHoldersOf(name), platformItem: () => undefined })
    -> 7 failed | 71 passed (78): (i) (ii) (iii) (v), plus the three platform CONTROLs
       (the platform-alone answer and the two #32534 owner-less rows), which read the platform too
A2  objectql callerlessFunctionResolution: return resolution unfiltered
    -> 1 failed | 77 passed (78): (iv)
A3  runtime releaseMovedFunctions: read the bare slot through resolveFunctionEntry again
    -> 2 failed | 40 passed (42): (vi), (x)
A4  runtime releaseFunctionClaims: return before releasing anything
    -> 10 failed | 32 passed (42): (vii) x2, (viii), (ix) x2, (x), and the four #32534 release pins
A11 runtime releaseMovedFunctions: release right after the unregister, before the kept functions are registered again
    -> 1 failed | 41 passed (42): (viii) alone — the card's epic-read measurement (34/34 green under A11) closed

Acceptance notes

Verification

Head d0af5314 (this branch's work on df9fe946, then origin/main at b4624440 merged once before opening; the merge touched none of this PR's files).

Local runs on d0af531 (objectql suite, typechecks and the two changed test files re-run after the merge, runtime's dependency closure rebuilt)
pnpm --filter @objectstack/objectql test                       595 files, 12988/12988 passed (12972/12972 before the merge, on 039e6bb2)
pnpm --filter @objectstack/objectql typecheck                  exit 0 (tsc + check:test-typecheck: 39 files / 233 errors / 64 pinned, held)
pnpm --filter @objectstack/runtime typecheck                   exit 0 (tsc + check:test-typecheck: 27 files / 183 errors / 66 pinned, held)
pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-request-family.test.ts    78/78
pnpm --filter @objectstack/runtime exec vitest run src/app-artifact-handlers.test.ts    42/42
eslint --no-inline-config on the six changed source and test files                       exit 0
Derived gate families (dispatch-gates --commands on d0af531: 72 families, the same list as on 039e6bb) — reconciled with --ran: 72 run, 0 NOT MEASURED, 0 UNRUN
72/72 exit 0 on d0af5314, among them: check-adr-0087-registration --base origin/main (1 declared-breaking changeset,
  BREAKING+bang+clause-2-narrowing, not-required (no-migration-prescription)), check-changeset-no-major,
  check-empty-changeset, check:changeset-gate-self-tests, check:engine-double-contract, check:durability-log-level,
  check:startup-registry-verdict, check:lean-entry-closure, check:facade-delegation-only, check:nul-bytes,
  check:type-check-coverage, check:type-check-debt, check:test-source-alias, check:cross-package-test-inputs,
  check:doc-authoring, check:issue-citations, check:published-files, check:dual-build-cjs-loads.
check-plugin-teardown-shape --self-test reads a fixture pinned to commit 621a4876, outside the shallow clone:
  that one commit was fetched (depth 1) before the run.
On 039e6bb2 (before the merge) 71/72 exit 0 and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, unbuilt dist/).

Declared to CI: the full runtime suite and its dependents (the addendum's scope: only the runtime file touched ran locally), the repo-wide pnpm lint, test:repo, and the full dogfood battery this ! title owes (NOT MEASURED locally: the dependents' run holds the shared verify lock for about 90 minutes).

Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382


🤖 Generated with Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 14, 2026
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

8 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 81 pages)
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 40 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ba35dfd78283d5f839eaaaab24764322ae3e263a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 05fd240c612e181a161a77f60e234be8ce5966bd — the merge of head a7b1de2a7510319114366953c7f169dbdd528a23 into base ba35dfd78283d5f839eaaaab24764322ae3e263a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 05fd240c612e181a161a77f60e234be8ce5966bd && git checkout 05fd240c612e181a161a77f60e234be8ce5966bd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ba35dfd78283d5f839eaaaab24764322ae3e263a a7b1de2a7510319114366953c7f169dbdd528a23 && git checkout -B drift-repro ba35dfd78283d5f839eaaaab24764322ae3e263a && git merge --no-ff a7b1de2a7510319114366953c7f169dbdd528a23

node scripts/docs-audit/affected-docs.mjs --json ba35dfd78283d5f839eaaaab24764322ae3e263a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cz4f7zEQZ2C9GTG382
Request: review · area E2b · head d0af531 · Fixes #32907 #32933 #32910

Area notes for the reviewer:

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head d0af531 · 2026-11-14T21:19:11Z

review#3.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d0af5314bc4190cc5d6e1076af0908657a1249df
Local-runs: none
Scope: packages/objectql/src/engine-hooks-and-functions.ts, packages/objectql/src/package-scoped-resolver.ts, packages/objectql/src/engine.ts, packages/runtime/src/app-artifact-handlers.ts, .changeset/32907-bare-function-read-platform-and-disabled-holders.md + packages/core/src/package-scoped-resolution.ts (resolvePackageScopedHolders, resolveEnvironmentWide, pickUnique, OVERRIDABLE_BASELINE_PACKAGES), packages/services/service-automation/src/function-package-scope.ts

① Derived judgments

  • objectql: a function's bare-name read refuses with 409 naming both when the platform and one package hold the name (ADR-0150 D4) #32907, the platform beside a package. bareFunctionEntry now hands every bare read to the rule (resolvePackageScopedHolders with callerPackageId: null) instead of counting non-null holders only. resolveEnvironmentWide already refuses ambiguous-in-environment when platformItem (the owner-less registration, functionHoldersOf) stands beside any package holder. So the bare read and the caller-less package-scoped read now give one answer, in every registration order. When it resolves, the bare map entry is still returned: registerFunction writes the same entry object to both maps, and unregisterFunctionsByPackage restores the one remaining holder's entry. Both maps are written only there (checked), so "resolved → functions.get(name)" holds.
  • objectql: the engine's function holder set follows ruling A's served-holders default #32933, a disabled package: counted, never resolved to. callerlessFunctionResolution rewrites exactly one case to not-found: resolved via unique-holder with a package holder that is disabled. I checked the claim that only a caller with no package reaches that step. A packaged caller exits at own, closure/baseline, platform, or a refusal (resolvePackageScopedHolders steps 1–3) and never gets unique-holder. So wrapping resolvePackageScopedFunction is a no-op for a packaged caller, and a flow of the disabled package still resolves its own function (pinned). function has no overridable baseline (OVERRIDABLE_BASELINE_PACKAGES.function is empty), so the baseline path the wrapper passes through cannot carry a disabled function either. Because refusals pass through unchanged, a disabled package still makes a name ambiguous and is named (ruling A, 6824698376), and the seal and bind-after-seal counts are untouched (CONTROL pins).
  • objectql: runtime pins for a function's bare-name claim release — pre-seal re-bind, move-path ordering, owner-spelling order (#32877 follow-up) #32910, the runtime re-bind. releaseMovedFunctions now puts back the package's own holder entry rather than resolveFunctionEntry. For a single-holder name that is the same object. Beside the platform, the old bare read would now throw and abort the teardown, and for a disabled package it would answer nothing and drop the owner spelling. Both are pinned. With this change, no production caller of ObjectQL#resolveFunction / resolveFunctionEntry remains in the repo (the flow script node goes through resolvePackageScopedFunction). The engine-level narrowing therefore lands on host or plugin code, and on environment-authored flows through the caller-less package-scoped read.
  • Declaration (your point 3). Clause-②: no (narrowing) with !, BREAKING and one adr-0087: not-required (no-migration-prescription) marker is accepted. The gate reads no (narrowing) as a truthful no carrying the arm (check-changeset-no-major.mjs). No method, parameter or export changes shape; what moves is which answer an existing call gets for a given registry state. That is the same reading I upheld on fix(service-messaging)!: the in-app message door counts only accepted deliveries and refuses a send that reached nobody #33347. The arm, not the value, drives the BREAKING section and the level, and patch is correct for a non-protocol change (AGENTS.md). Metadata-protocol: none holds.
  • nit N1: callerlessFunctionResolution is exported from package-scoped-resolver.ts but not re-exported from index.ts, which is fine. Its docblock says "the step only such a caller reaches". Add a pointer to resolvePackageScopedHolders steps 1–3, so the next edit of the core rule knows this wrapper relies on it.

② Semver level

@objectstack/objectql: patch, @objectstack/runtime: patch, fix(objectql, runtime)!, Clause-②: no (narrowing), BREAKING with the remedy (address by package, rename, or enable), one ADR-0087 marker. Correct.

③ Boundary flags

  • The ruling on the disabled half. You implemented ruling A's "counted" plus "never resolved to", and sent objectql: the engine's function holder set follows ruling A's served-holders default #32933's served-only alternative to the seat as a needs-ruling. If the seat picks served-only, the disabled half changes again: a second BREAKING note on the same read. Land after the seat answers that needs-ruling, or after it confirms that this reading stands.
  • Draft, and CI not complete at this head (read 21:30Z). The ACCEPT re-reads every check at the landing head. The objectql and runtime suites and the dogfood battery owed by the narrowing arm must be green there.

④ Design judgment

Routing the bare read through the one rule, and not keeping a second "count non-null holders" copy, removes the platform-order bug at its source (one code path, as ADR-0150 D3 intends). The disabled-package rule is a thin, single-case filter over the rule's answer, and it keeps every refusal. That is the smallest reading consistent with ruling A. Replacing the re-bind's bare read with the holder entry it already had removes a dependency on a resolution where it only needed a slot. The design holds.

Implemented-by: claude/area-E2b-32907
Reviewed-by: session_016WV4d1PGptBW3QRQNmy5e6

VERDICT: PASS

…teps it relies on

Review nit N1: a caller package stops at resolvePackageScopedHolders'
steps 1-3; only the environment-wide branch answers unique-holder.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Review carry — PASS stands

Head-sha: a7b1de2a7510319114366953c7f169dbdd528a23

I read the delta d0af5314...a7b1de2a. It adds 6 lines to the callerlessFunctionResolution docblock in package-scoped-resolver.ts, all comment, with no code change. The docblock now names resolvePackageScopedHolders steps 1–3 and says only the environment-wide branch answers unique-holder, which closes N1. The ③ landing condition is met: the seat confirmed that ruling A (6824698376) stands for the disabled half. The contract-review PASS at d0af5314bc4190cc5d6e1076af0908657a1249df carries to this head (Regen-provenance: comment-only move). Before landing, every check at the landing head must be green, including the dogfood battery that the narrowing arm requires.

Reviewed-by: session_016WV4d1PGptBW3QRQNmy5e6 (review#3)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cz4f7zEQZ2C9GTG382
ACCEPT · area E2b (area-lease pilot #33059) · 2026-11-14T22:12Z

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 14, 2026 22:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 14, 2026
Any commits made after this event will not be merged.
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 43647010018 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Dogfood Regression Gate (3/3) — 失败步骤: Boot example apps and exercise real user flows

    @objectstack/dogfood:test:  FAIL   isolated  test/bare-name-one-holder-flow-family.dogfood.test.ts > one package holder per flow, webhook, connector and email template name, on a booted showcase > the
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: one warning line: expected [] to have a length of 1 but got +0
    @objectstack/dogfood:test:  FAIL   isolated  test/bare-name-one-holder-flow-family.dogfood.test.ts > one package holder per flow, webhook, connector and email template name, on a booted showcase > ins
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: {"success":true,"data":{"manifestId":"com.qa.bare.delta","version":"1.0.0","versionId":"1.0.0","installedAt":"2026-11-14T22:18:36.089Z","hotLoaded":true,"upg
    @objectstack/dogfood:test:  FAIL   isolated  test/bare-name-one-holder-flow-family.dogfood.test.ts > one package holder per flow, webhook, connector and email template name, on a booted showcase > the
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: expected [ Array(3) ] to deeply equal [ Array(2) ]
    @objectstack/dogfood:test:  FAIL   isolated  test/bare-name-one-holder-flow-family.dogfood.test.ts > one package holder per flow, webhook, connector and email template name, on a booted showcase > the
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: expected [ Array(3) ] to deeply equal [ Array(2) ]
    @objectstack/dogfood:test:  FAIL   isolated  test/bare-name-one-holder-flow-family.dogfood.test.ts > one package holder per flow, webhook, connector and email template name, on a booted showcase > the
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: expected [ [ 'com.qa.bare.alpha', true ], …(2) ] to deeply equal [ [ 'com.qa.bare.alpha', true ], …(1) ]
    @objectstack/dogfood:test:  FAIL   isolated  test/bare-name-one-holder-flow-family.dogfood.test.ts > one package holder per flow, webhook, connector and email template name, on a booted showcase > the
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: expected [ 'com.qa.bare.alpha', …(3) ] to deeply equal [ 'com.qa.bare.alpha', …(2) ]
    
  • Test Core (7/8) — 失败步骤: Run this shard's tests

    @objectstack/plugin-approvals:test:  FAIL  src/action-page-member.integration.test.ts > [#22578] handleActionPage — the ADR-0043 action page from a Request > GET renders the confirm page for a live to
      ↳ 失败原因: @objectstack/plugin-approvals:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 18 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 43647280081 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (7/8) — 失败步骤: Run this shard's tests

    @objectstack/plugin-approvals:test:  FAIL  src/action-page-member.integration.test.ts > [#22578] handleActionPage — the ADR-0043 action page from a Request > GET renders the confirm page for a live to
      ↳ 失败原因: @objectstack/plugin-approvals:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • src/action-page-member.integration.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 18 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cz4f7zEQZ2C9GTG382
Re-queue once · area E2b · head a7b1de2a unchanged · 2026-11-14T23:00Z

Triage of the two queue builds (each group = main + this PR only):

  • 43647010018 (base 1314a91): Dogfood Regression Gate (3/3), bare-name-one-holder-flow-family.dogfood.test.ts (connector boot warning / install-local refusal). Not reproduced: the next build 43647280081 (base f068cde, same PR head) ran all three dogfood shards green.
  • 43647280081: Test Core (7/8), @objectstack/plugin-approvals src/action-page-member.integration.test.ts — Test timed out in 5000ms. A timeout in a package this PR does not touch (objectql, runtime only).

Neither signature points at this PR's change; re-armed once. A repeat of either signature on the next build ⇒ stop and hand to the seat.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants